package com.novoda.runbuddy.provider;

import java.util.HashMap;
import java.util.Map;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;

public class RoutesProvider extends IRBContentProvider {

	public RoutesProvider(ContentProvider cp, SQLiteOpenHelper mOpenHelper) {
		super(cp, mOpenHelper);
	}

	private static Map<String, String>	ROUTE_LIST_PROJECTION_MAP		= null;
	private static Map<String, String>	ROUTE_POINT_LIST_PROJECTION_MAP	= null;

	private static final String			ROUTE_LOCATION_JOIN				= "route LEFT OUTER JOIN geoLocation ON (route._idGeolocation = geoLocation._id)";
	private static final String			ROUTE_POINT_LOCATION_JOIN		= "routePoint LEFT OUTER JOIN geoLocation ON (routePoint._idGeolocation = geoLocation._id)";

	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

		SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
		Cursor c = null;

		int match = URI_MATCHER.match(uri);
		switch (match) {
			case ROUTES:
				qb.setTables(ROUTE_LOCATION_JOIN);
				qb.setProjectionMap(ROUTE_LIST_PROJECTION_MAP);
				break;
			case ROUTE_ID:
				qb.setTables(ROUTE_LOCATION_JOIN);
				qb.setProjectionMap(ROUTE_LIST_PROJECTION_MAP);
				qb.appendWhere("route._id=" + uri.getPathSegments().get(1));
				break;
			case ROUTE_POINTS:
				qb.setTables(ROUTE_POINT_LOCATION_JOIN);
				qb.setProjectionMap(ROUTE_POINT_LIST_PROJECTION_MAP);
				qb.appendWhere("_idRoute=" + uri.getPathSegments().get(1));
				break;
			default:
				throw new IllegalArgumentException("Unknown URL " + uri);
		}

		c = qb.query(getReadableDatabase(), projection, selection, selectionArgs, null, null, sortOrder);
		c.setNotificationUri(getContext().getContentResolver(), uri);
		return c;
	}

	public int delete(Uri uri, String selection, String[] selectionArgs) {
		int count;
		@SuppressWarnings("unused")
		long rowId = 0;
		switch (URI_MATCHER.match(uri)) {
			case ROUTES:
				count = getWritableDatabase().delete("route", selection, selectionArgs);
				break;

			case ROUTE_ID:
				String segment = uri.getPathSegments().get(1);
				rowId = Long.parseLong(segment);
				count = getWritableDatabase().delete("route",
						"_id=" + segment + (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
				break;

			default:
				throw new IllegalArgumentException("Unknown URL " + uri);
		}

		getContext().getContentResolver().notifyChange(uri, null);
		return count;

	}

	public String getType(Uri uri) {
		int match = URI_MATCHER.match(uri);
		switch (match) {
			case ROUTES:
				return "vnd.android.cursor.dir/vnd.runbuddy.route";
			case ROUTE_ID:
				return "vnd.android.cursor.item/vnd.runbuddy.route";
			default:
				throw new IllegalArgumentException("Unknown URL " + uri);
		}
	}

	public Uri insert(Uri uri, ContentValues values) {
		int match = URI_MATCHER.match(uri);
		switch (match) {
			case ROUTES:
				return insertRoute(values);
			case ROUTE_POINTS:
				int routeId = Integer.parseInt(uri.getPathSegments().get(1));
				return insertPointForRoute(routeId, values);
			default:
				throw new IllegalArgumentException("Unknown URL " + uri);
		}
	}

	private Uri insertPointForRoute(int routeId, ContentValues initialValues) {
		long rowID;
		long geolocationID;
		ContentValues values;
		if (initialValues != null) {
			values = new ContentValues(initialValues);
		} else {
			values = new ContentValues();
		}
		values.put("_idRoute", routeId);
		Long now = Long.valueOf(System.currentTimeMillis());

		if (values.containsKey(RunBuddyCP.RoutePoints.CREATED_DATE) == false) {
			values.put(RunBuddyCP.RoutePoints.CREATED_DATE, now);
		}

		if (values.containsKey(RunBuddyCP.RoutePoints.MODIFIED_DATE) == false) {
			values.put(RunBuddyCP.RoutePoints.MODIFIED_DATE, now);
		}

		// Can not insert without latitude/longitude
		if (values.containsKey(RunBuddyCP.RoutePoints.LATITUDE) == false
				|| values.containsKey(RunBuddyCP.RoutePoints.LONGITUDE) == false)
			return null;

		geolocationID = createGeoLocation(values.getAsLong(RunBuddyCP.RoutePoints.LATITUDE), values
				.getAsLong(RunBuddyCP.RoutePoints.LONGITUDE));

		values.remove(RunBuddyCP.RoutePoints.LATITUDE);
		values.remove(RunBuddyCP.RoutePoints.LONGITUDE);
		values.put("_idGeoLocation", geolocationID);

		rowID = getWritableDatabase().insert("routePoint", "name", values);
		if (rowID > 0) {
			getWritableDatabase().update("routePoint", values, "_id=" + rowID, null);
			Uri uri = ContentUris.appendId(RunBuddyCP.Routes.CONTENT_URI.buildUpon(), routeId).build();
			uri.buildUpon().appendPath("points").appendPath("" + rowID);
			getContext().getContentResolver().notifyChange(uri, null);
			return uri;
		}
		throw new SQLException("Failed to insert row into routePoint");
	}

	private Uri insertRoute(ContentValues initialValues) {
		long rowID;
		ContentValues values;
		if (initialValues != null) {
			values = new ContentValues(initialValues);
		} else {
			values = new ContentValues();
		}
		Long now = Long.valueOf(System.currentTimeMillis());
		
		if (values.containsKey(RunBuddyCP.Routes.NAME) == false) {
			values.put(RunBuddyCP.Routes.NAME, getContext().getString(android.R.string.untitled));
		}

		// TODO will need to think about this...
		if (values.containsKey(RunBuddyCP.Routes.SERVER_ID) == false) {
			values.put(RunBuddyCP.Routes.SERVER_ID, 1);
		}

		if (values.containsKey(RunBuddyCP.Routes.CREATED_DATE) == false) {
			values.put(RunBuddyCP.Routes.CREATED_DATE, now);
		}

		if (values.containsKey(RunBuddyCP.Routes.MODIFIED_DATE) == false) {
			values.put(RunBuddyCP.Routes.MODIFIED_DATE, now);
		}

		// mDB.compileStatement("BEGIN TRANSACTION");
		rowID = getWritableDatabase().insert("route", "name", values);
		if (rowID > 0) {

			addCenterGeoLocation(rowID, values);

			getWritableDatabase().update("route", values, "_id=" + rowID, null);
			Uri uri = ContentUris.appendId(RunBuddyCP.Routes.CONTENT_URI.buildUpon(), rowID).build();

			// mDB.compileStatement("END TRANSACTION");

			getContext().getContentResolver().notifyChange(uri, null);
			return uri;
		}
		throw new SQLException("Failed to insert row into routes");
	}

	private void addCenterGeoLocation(long rowID, ContentValues values) {
		ContentValues geoValues = new ContentValues();

		// Latitude/longitude part computed from the list of routePoints.
		if (values.containsKey(RunBuddyCP.Routes.LATITUDE) == false || values.containsKey(RunBuddyCP.Routes.LONGITUDE) == false) {
			long[] center = computeCenterOfRoute(rowID);
			geoValues.put(RunBuddyCP.Routes.LATITUDE, center[0]);
			geoValues.put(RunBuddyCP.Routes.LONGITUDE, center[1]);
		} else {
			geoValues.put(RunBuddyCP.Routes.LATITUDE, values.getAsLong(RunBuddyCP.Routes.LATITUDE));
			geoValues.put(RunBuddyCP.Routes.LONGITUDE, values.getAsLong(RunBuddyCP.Routes.LONGITUDE));
		}

		values.remove(RunBuddyCP.Routes.LATITUDE);
		values.remove(RunBuddyCP.Routes.LONGITUDE);

		long geoLocationRowID = getWritableDatabase().insert("geoLocation", "name", geoValues);
		values.put("_idGeolocation", geoLocationRowID);
	}

	/**
	 * This method uses the SQLite database to compute the minimum and maximum
	 * values from a list of geoLocation knowing the route id.
	 * 
	 * @param routeId
	 *            the route id against which we will compute the center of the
	 *            map
	 * @return an array of 2 ints, the first is the latitude center and the
	 *         second the longitude. -1 if it can not be computed.
	 */
	protected long[] computeCenterOfRoute(long routeId) {
		long[] ret = new long[] { -1l, -1l };

		final int MAX_LATITUDE_CURSOR_INDEX = 0;
		final int MIN_LATITUDE_CURSOR_INDEX = 1;

		final int MAX_LONGITUDE_CURSOR_INDEX = 2;
		final int MIN_LONGITUDE_CURSOR_INDEX = 3;

		Cursor c = null;
		// using SQL to get the min and max for longitude and latitude. from
		// there get the center
		String sql = "SELECT max(latitude), min(latitude), max(longitude), min(longitude) "
				+ "from geoLocation LEFT OUTER JOIN routePoint ON (routePoint._idGeolocation = geoLocation._id) "
				+ "WHERE routePoint._idRoute=?";
		c = getReadableDatabase().rawQuery(sql, new String[] { ("" + routeId) });

		if (c.moveToFirst()) {
			ret[0] = (long) c.getLong(MIN_LATITUDE_CURSOR_INDEX)
					+ (c.getLong(MAX_LATITUDE_CURSOR_INDEX) - c.getLong(MIN_LATITUDE_CURSOR_INDEX)) / 2;
			ret[1] = (long) c.getLong(MIN_LONGITUDE_CURSOR_INDEX)
					+ (c.getLong(MAX_LONGITUDE_CURSOR_INDEX) - c.getLong(MIN_LONGITUDE_CURSOR_INDEX)) / 2;
		}
		c.close();
		return ret;
	}

	@Override
	public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
		// TODO Auto-generated method stub
		return 0;
	}

	static {
		ROUTE_LIST_PROJECTION_MAP = new HashMap<String, String>();
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes._ID, "route._id");
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.NAME, "route.name");
		// GeoLocation_id is replaced with the actual values from the
		// GeoLocation (lat/long in this case)
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.LATITUDE, "geoLocation.latitude");
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.LONGITUDE, "geoLocation.longitude");
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.SERVER_ID, "route.__id");
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.CREATED_DATE, "route.createdAt");
		ROUTE_LIST_PROJECTION_MAP.put(RunBuddyCP.Routes.MODIFIED_DATE, "route.modifiedAt");

		ROUTE_POINT_LIST_PROJECTION_MAP = new HashMap<String, String>();
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints._ID, "routePoint._id");
		ROUTE_POINT_LIST_PROJECTION_MAP.put("_idRoute", "_idRoute");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.LATITUDE, "geoLocation.latitude");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.LONGITUDE, "geoLocation.longitude");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.POINT_NO, "pointNo");
		// TODO
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.ALGORITHM, "algorithm");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.DIRECTION, "direction");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.CREATED_DATE, "routePoint.createdAt");
		ROUTE_POINT_LIST_PROJECTION_MAP.put(RunBuddyCP.RoutePoints.MODIFIED_DATE, "routePoint.modifiedAt");
	}
}
